# -*- mode: org; fill-column: 100; -*-
#+TITLE: jq guide
#+AUTHOR: Zelphir Kaltstahl
#+DATE: [2023-04-13 Thu]
#+LANGUAGE: en
#+KEYWORDS: tool command-line JSON jq
#+EXCLUDE_TAGS: noexport, notexported
#+CREATOR: Emacs
#+STARTUP: overview
#+STARTUP: indent
#+STARTUP: align
#+STARTUP: shrink
#+STARTUP: inlineimages
#+STARTUP: entitiesplain
#+STARTUP: nologdone
#+STARTUP: nologreschedule
#+STARTUP: nologredeadline
#+STARTUP: nologrefile
#+PRIORITIES: A E E
#+TODO: TODO VALIDATE MAYBE WIP TESTING | DONE VALID CANCELED POSTPONED OBSOLETE
#+OPTIONS: ^:{}
#+OPTIONS: tags:nil
#+OPTIONS: tasks:t
#+OPTIONS: toc:2
#+OPTIONS: H:10
#+OPTIONS: p:nil
#+OPTIONS: pri:nil
#+OPTIONS: prop:nil
#+OPTIONS: todo:nil
#+OPTIONS: stat:nil
#+OPTIONS: |:t
#+OPTIONS: inline:nil

* What is jq?

+ command line tool for processing JSON

* What operations does it offer?
:PROPERTIES:
:header-args: :var container_name="jq-test-container" :results output verbatim replace drawer
:END:

** Preparation

To show some operations =jq= provides, lets start a docker container:

#+begin_src sh
docker rm --force "${container_name}" || true;
docker run --detach --volume ./foo:/bar --volume ./qux:/qox --name "${container_name}" python:3.10
#+end_src

#+RESULTS:
:results:
js-test-container
78e44950b6606aa337c9b2389e19ecd89552837049dda5500ca8ea69dcd40953
:end:

** Selection

*** Current layer

=.= selects the current layer, not changing anything, but gives syntax highlighting and proper formatting for unformatted JSON:

#+begin_src sh
docker inspect "${container_name}" | jq '.'
#+end_src

#+RESULTS:
:results:
[
  {
    "Id": "30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f",
    "Created": "2023-04-13T16:04:23.596048533Z",
    "Path": "python3",
    "Args": [],
    "State": {
      "Status": "exited",
      "Running": false,
      "Paused": false,
      "Restarting": false,
      "OOMKilled": false,
      "Dead": false,
      "Pid": 0,
      "ExitCode": 0,
      "Error": "",
      "StartedAt": "2023-04-13T16:04:23.922093384Z",
      "FinishedAt": "2023-04-13T16:04:23.953176332Z"
    },
    "Image": "sha256:c339f65d6ddf665efdc389c8eb69fe4bc3028f82210019336ba8b98126a70aed",
    "ResolvConfPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/resolv.conf",
    "HostnamePath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/hostname",
    "HostsPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/hosts",
    "LogPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f-json.log",
    "Name": "/js-test-container",
    "RestartCount": 0,
    "Driver": "overlay2",
    "Platform": "linux",
    "MountLabel": "",
    "ProcessLabel": "",
    "AppArmorProfile": "docker-default",
    "ExecIDs": null,
    "HostConfig": {
      "Binds": [
        "/home/hans/dev/stackfuel/tech-talk/future/jq/qux:/qox",
        "/home/hans/dev/stackfuel/tech-talk/future/jq/foo:/bar"
      ],
      "ContainerIDFile": "",
      "LogConfig": {
        "Type": "json-file",
        "Config": {}
      },
      "NetworkMode": "default",
      "PortBindings": {},
      "RestartPolicy": {
        "Name": "no",
        "MaximumRetryCount": 0
      },
      "AutoRemove": false,
      "VolumeDriver": "",
      "VolumesFrom": null,
      "ConsoleSize": [
        0,
        0
      ],
      "CapAdd": null,
      "CapDrop": null,
      "CgroupnsMode": "private",
      "Dns": [],
      "DnsOptions": [],
      "DnsSearch": [],
      "ExtraHosts": null,
      "GroupAdd": null,
      "IpcMode": "private",
      "Cgroup": "",
      "Links": null,
      "OomScoreAdj": 0,
      "PidMode": "",
      "Privileged": false,
      "PublishAllPorts": false,
      "ReadonlyRootfs": false,
      "SecurityOpt": null,
      "UTSMode": "",
      "UsernsMode": "",
      "ShmSize": 67108864,
      "Runtime": "runc",
      "Isolation": "",
      "CpuShares": 0,
      "Memory": 0,
      "NanoCpus": 0,
      "CgroupParent": "",
      "BlkioWeight": 0,
      "BlkioWeightDevice": [],
      "BlkioDeviceReadBps": [],
      "BlkioDeviceWriteBps": [],
      "BlkioDeviceReadIOps": [],
      "BlkioDeviceWriteIOps": [],
      "CpuPeriod": 0,
      "CpuQuota": 0,
      "CpuRealtimePeriod": 0,
      "CpuRealtimeRuntime": 0,
      "CpusetCpus": "",
      "CpusetMems": "",
      "Devices": [],
      "DeviceCgroupRules": null,
      "DeviceRequests": null,
      "MemoryReservation": 0,
      "MemorySwap": 0,
      "MemorySwappiness": null,
      "OomKillDisable": null,
      "PidsLimit": null,
      "Ulimits": null,
      "CpuCount": 0,
      "CpuPercent": 0,
      "IOMaximumIOps": 0,
      "IOMaximumBandwidth": 0,
      "MaskedPaths": [
        "/proc/asound",
        "/proc/acpi",
        "/proc/kcore",
        "/proc/keys",
        "/proc/latency_stats",
        "/proc/timer_list",
        "/proc/timer_stats",
        "/proc/sched_debug",
        "/proc/scsi",
        "/sys/firmware"
      ],
      "ReadonlyPaths": [
        "/proc/bus",
        "/proc/fs",
        "/proc/irq",
        "/proc/sys",
        "/proc/sysrq-trigger"
      ]
    },
    "GraphDriver": {
      "Data": {
        "LowerDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb-init/diff:/var/lib/docker/overlay2/706905c2306dee20fe42505fbe935efff92e20c3ccfed79fed124fc6448807cf/diff:/var/lib/docker/overlay2/92bc37b80515b3811e6a01adf9df1580bf0d2c4e64708c030c59d820f7a37d72/diff:/var/lib/docker/overlay2/d6ffd7a2a13f617e930fe98a75bdeed3677958b9d8ee6b64b72247924858cf1a/diff:/var/lib/docker/overlay2/724038b50427faa35d2ace0128880e9a435c1a36aeca9eefd6a275f8bc57a7d4/diff:/var/lib/docker/overlay2/df12bfeb1e8b2a65890d312018d7114dba7198833a2eb044dad113b6e8b8ae47/diff:/var/lib/docker/overlay2/b376490f05031c1243af53b896afc6a29b57695826e00ec077ea23af106b7510/diff:/var/lib/docker/overlay2/9c714131059e6469c1e3746f0b4a2640c15ae25ea83d180786e7d944c3e85fad/diff:/var/lib/docker/overlay2/036356998ba287549cea161d2c6049a15a14f55b99643a63a7c107a83e673fe5/diff:/var/lib/docker/overlay2/951f57df8a80a8693e190753e1e299776ed59473832f235d20a546f93e53f902/diff",
        "MergedDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/merged",
        "UpperDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/diff",
        "WorkDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/work"
      },
      "Name": "overlay2"
    },
    "Mounts": [
      {
        "Type": "bind",
        "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
        "Destination": "/qox",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
      },
      {
        "Type": "bind",
        "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
        "Destination": "/bar",
        "Mode": "",
        "RW": true,
        "Propagation": "rprivate"
      }
    ],
    "Config": {
      "Hostname": "30de38326724",
      "Domainname": "",
      "User": "",
      "AttachStdin": false,
      "AttachStdout": false,
      "AttachStderr": false,
      "Tty": false,
      "OpenStdin": false,
      "StdinOnce": false,
      "Env": [
        "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
        "LANG=C.UTF-8",
        "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
        "PYTHON_VERSION=3.10.11",
        "PYTHON_PIP_VERSION=23.0.1",
        "PYTHON_SETUPTOOLS_VERSION=65.5.1",
        "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d5cb0afaf23b8520f1bbcfed521017b4a95f5c01/public/get-pip.py",
        "PYTHON_GET_PIP_SHA256=394be00f13fa1b9aaa47e911bdb59a09c3b2986472130f30aa0bfaf7f3980637"
      ],
      "Cmd": [
        "python3"
      ],
      "Image": "python:3.10",
      "Volumes": null,
      "WorkingDir": "",
      "Entrypoint": null,
      "OnBuild": null,
      "Labels": {}
    },
    "NetworkSettings": {
      "Bridge": "",
      "SandboxID": "9cea39c6b01f6d0e29ddd9066ca53cebef188a0d4a8467d783ae4beb15bcd749",
      "HairpinMode": false,
      "LinkLocalIPv6Address": "",
      "LinkLocalIPv6PrefixLen": 0,
      "Ports": {},
      "SandboxKey": "/var/run/docker/netns/9cea39c6b01f",
      "SecondaryIPAddresses": null,
      "SecondaryIPv6Addresses": null,
      "EndpointID": "",
      "Gateway": "",
      "GlobalIPv6Address": "",
      "GlobalIPv6PrefixLen": 0,
      "IPAddress": "",
      "IPPrefixLen": 0,
      "IPv6Gateway": "",
      "MacAddress": "",
      "Networks": {
        "bridge": {
          "IPAMConfig": null,
          "Links": null,
          "Aliases": null,
          "NetworkID": "212bec1a9fc25041273548dad126a03609595beac973321f88cfa0a468df4fe4",
          "EndpointID": "",
          "Gateway": "",
          "IPAddress": "",
          "IPPrefixLen": 0,
          "IPv6Gateway": "",
          "GlobalIPv6Address": "",
          "GlobalIPv6PrefixLen": 0,
          "MacAddress": "",
          "DriverOpts": null
        }
      }
    }
  }
]
:end:

*** Array index

=docker inspect= actually returns an array. But there is only 1 object inside of it. Lets select the first object:

#+begin_src sh
docker inspect "${container_name}" | jq '.[0]'
#+end_src

#+RESULTS:
:results:
{
  "Id": "30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f",
  "Created": "2023-04-13T16:04:23.596048533Z",
  "Path": "python3",
  "Args": [],
  "State": {
    "Status": "exited",
    "Running": false,
    "Paused": false,
    "Restarting": false,
    "OOMKilled": false,
    "Dead": false,
    "Pid": 0,
    "ExitCode": 0,
    "Error": "",
    "StartedAt": "2023-04-13T16:04:23.922093384Z",
    "FinishedAt": "2023-04-13T16:04:23.953176332Z"
  },
  "Image": "sha256:c339f65d6ddf665efdc389c8eb69fe4bc3028f82210019336ba8b98126a70aed",
  "ResolvConfPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/resolv.conf",
  "HostnamePath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/hostname",
  "HostsPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/hosts",
  "LogPath": "/var/lib/docker/containers/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f/30de38326724658ac76a7f80231f360400e9f7877dcb94e4b6907fd46bfbf73f-json.log",
  "Name": "/js-test-container",
  "RestartCount": 0,
  "Driver": "overlay2",
  "Platform": "linux",
  "MountLabel": "",
  "ProcessLabel": "",
  "AppArmorProfile": "docker-default",
  "ExecIDs": null,
  "HostConfig": {
    "Binds": [
      "/home/hans/dev/stackfuel/tech-talk/future/jq/qux:/qox",
      "/home/hans/dev/stackfuel/tech-talk/future/jq/foo:/bar"
    ],
    "ContainerIDFile": "",
    "LogConfig": {
      "Type": "json-file",
      "Config": {}
    },
    "NetworkMode": "default",
    "PortBindings": {},
    "RestartPolicy": {
      "Name": "no",
      "MaximumRetryCount": 0
    },
    "AutoRemove": false,
    "VolumeDriver": "",
    "VolumesFrom": null,
    "ConsoleSize": [
      0,
      0
    ],
    "CapAdd": null,
    "CapDrop": null,
    "CgroupnsMode": "private",
    "Dns": [],
    "DnsOptions": [],
    "DnsSearch": [],
    "ExtraHosts": null,
    "GroupAdd": null,
    "IpcMode": "private",
    "Cgroup": "",
    "Links": null,
    "OomScoreAdj": 0,
    "PidMode": "",
    "Privileged": false,
    "PublishAllPorts": false,
    "ReadonlyRootfs": false,
    "SecurityOpt": null,
    "UTSMode": "",
    "UsernsMode": "",
    "ShmSize": 67108864,
    "Runtime": "runc",
    "Isolation": "",
    "CpuShares": 0,
    "Memory": 0,
    "NanoCpus": 0,
    "CgroupParent": "",
    "BlkioWeight": 0,
    "BlkioWeightDevice": [],
    "BlkioDeviceReadBps": [],
    "BlkioDeviceWriteBps": [],
    "BlkioDeviceReadIOps": [],
    "BlkioDeviceWriteIOps": [],
    "CpuPeriod": 0,
    "CpuQuota": 0,
    "CpuRealtimePeriod": 0,
    "CpuRealtimeRuntime": 0,
    "CpusetCpus": "",
    "CpusetMems": "",
    "Devices": [],
    "DeviceCgroupRules": null,
    "DeviceRequests": null,
    "MemoryReservation": 0,
    "MemorySwap": 0,
    "MemorySwappiness": null,
    "OomKillDisable": null,
    "PidsLimit": null,
    "Ulimits": null,
    "CpuCount": 0,
    "CpuPercent": 0,
    "IOMaximumIOps": 0,
    "IOMaximumBandwidth": 0,
    "MaskedPaths": [
      "/proc/asound",
      "/proc/acpi",
      "/proc/kcore",
      "/proc/keys",
      "/proc/latency_stats",
      "/proc/timer_list",
      "/proc/timer_stats",
      "/proc/sched_debug",
      "/proc/scsi",
      "/sys/firmware"
    ],
    "ReadonlyPaths": [
      "/proc/bus",
      "/proc/fs",
      "/proc/irq",
      "/proc/sys",
      "/proc/sysrq-trigger"
    ]
  },
  "GraphDriver": {
    "Data": {
      "LowerDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb-init/diff:/var/lib/docker/overlay2/706905c2306dee20fe42505fbe935efff92e20c3ccfed79fed124fc6448807cf/diff:/var/lib/docker/overlay2/92bc37b80515b3811e6a01adf9df1580bf0d2c4e64708c030c59d820f7a37d72/diff:/var/lib/docker/overlay2/d6ffd7a2a13f617e930fe98a75bdeed3677958b9d8ee6b64b72247924858cf1a/diff:/var/lib/docker/overlay2/724038b50427faa35d2ace0128880e9a435c1a36aeca9eefd6a275f8bc57a7d4/diff:/var/lib/docker/overlay2/df12bfeb1e8b2a65890d312018d7114dba7198833a2eb044dad113b6e8b8ae47/diff:/var/lib/docker/overlay2/b376490f05031c1243af53b896afc6a29b57695826e00ec077ea23af106b7510/diff:/var/lib/docker/overlay2/9c714131059e6469c1e3746f0b4a2640c15ae25ea83d180786e7d944c3e85fad/diff:/var/lib/docker/overlay2/036356998ba287549cea161d2c6049a15a14f55b99643a63a7c107a83e673fe5/diff:/var/lib/docker/overlay2/951f57df8a80a8693e190753e1e299776ed59473832f235d20a546f93e53f902/diff",
      "MergedDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/merged",
      "UpperDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/diff",
      "WorkDir": "/var/lib/docker/overlay2/e62792604f642255dd442603bbc5e3fc03411d74a383f433f69e6365708174cb/work"
    },
    "Name": "overlay2"
  },
  "Mounts": [
    {
      "Type": "bind",
      "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
      "Destination": "/qox",
      "Mode": "",
      "RW": true,
      "Propagation": "rprivate"
    },
    {
      "Type": "bind",
      "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
      "Destination": "/bar",
      "Mode": "",
      "RW": true,
      "Propagation": "rprivate"
    }
  ],
  "Config": {
    "Hostname": "30de38326724",
    "Domainname": "",
    "User": "",
    "AttachStdin": false,
    "AttachStdout": false,
    "AttachStderr": false,
    "Tty": false,
    "OpenStdin": false,
    "StdinOnce": false,
    "Env": [
      "PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      "LANG=C.UTF-8",
      "GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D",
      "PYTHON_VERSION=3.10.11",
      "PYTHON_PIP_VERSION=23.0.1",
      "PYTHON_SETUPTOOLS_VERSION=65.5.1",
      "PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/d5cb0afaf23b8520f1bbcfed521017b4a95f5c01/public/get-pip.py",
      "PYTHON_GET_PIP_SHA256=394be00f13fa1b9aaa47e911bdb59a09c3b2986472130f30aa0bfaf7f3980637"
    ],
    "Cmd": [
      "python3"
    ],
    "Image": "python:3.10",
    "Volumes": null,
    "WorkingDir": "",
    "Entrypoint": null,
    "OnBuild": null,
    "Labels": {}
  },
  "NetworkSettings": {
    "Bridge": "",
    "SandboxID": "9cea39c6b01f6d0e29ddd9066ca53cebef188a0d4a8467d783ae4beb15bcd749",
    "HairpinMode": false,
    "LinkLocalIPv6Address": "",
    "LinkLocalIPv6PrefixLen": 0,
    "Ports": {},
    "SandboxKey": "/var/run/docker/netns/9cea39c6b01f",
    "SecondaryIPAddresses": null,
    "SecondaryIPv6Addresses": null,
    "EndpointID": "",
    "Gateway": "",
    "GlobalIPv6Address": "",
    "GlobalIPv6PrefixLen": 0,
    "IPAddress": "",
    "IPPrefixLen": 0,
    "IPv6Gateway": "",
    "MacAddress": "",
    "Networks": {
      "bridge": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": null,
        "NetworkID": "212bec1a9fc25041273548dad126a03609595beac973321f88cfa0a468df4fe4",
        "EndpointID": "",
        "Gateway": "",
        "IPAddress": "",
        "IPPrefixLen": 0,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "",
        "DriverOpts": null
      }
    }
  }
}
:end:

*** Object key

Often we are interested in the mounts of a container. The following command selects the =Mounts= attribute:

#+begin_src sh
docker inspect "${container_name}" | jq '.[0] | .Mounts'
#+end_src

#+RESULTS:
:results:
[
  {
    "Type": "bind",
    "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
    "Destination": "/qox",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  },
  {
    "Type": "bind",
    "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
    "Destination": "/bar",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  }
]
:end:

The ~|~ acts as a pipe, applying the next operation for all elements of the previous step.

** Construction from selection

Lets say we only want to see the =Source= and =Destination= attribute and get rid of all not needed attributes in the output, constructing new JSON objects:

#+begin_src sh
docker inspect "${container_name}" | jq '.[0] | .Mounts | .[] | {Source , Destination}'
#+end_src

#+RESULTS:
:results:
{
  "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
  "Destination": "/qox"
}
{
  "Source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
  "Destination": "/bar"
}
:end:

~docker inspect "${container_name}" | jq '.[0] | .Mounts'~ returns an array.

We need to tell =jq=, that we want to do something for each of the items in the array. This is done with ~| .[]~. Instead of one big array, subsequent actions will deal with separate array elements.

~{attr1, attr2}~ specifies how we want to construct a new object. In this case we simply select the only 2 attributes, which shall be in the resulting object.

We could also rename the attributes as follows:

#+begin_src sh
docker inspect "${container_name}" | jq '.[0] | .Mounts | .[] | {source: .Source , destination: .Destination}'
#+end_src

#+RESULTS:
:results:
{
  "source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
  "destination": "/bar"
}
{
  "source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
  "destination": "/qox"
}
:end:

Note the lower-case keys.

** Calculations

=jq= can calculate!

*** Simple

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4
}
EOF
} | jq '(.a + 2) * 5'
#+end_src

#+RESULTS:
:results:
25
:end:

*** When constructing objects

Use some calculated value inside a newly constructed JSON object:

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4
}
EOF
} | jq '. | {"calculated-value": ((.a + 2) * 5), b}'
#+end_src

#+RESULTS:
:results:
{
  "calculated-value": 25,
  "b": 4
}
:end:

*** Using multiple keys

And even involve multiple existing values in a calculation:

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4
}
EOF
} | jq '. | {"calculated-value": ((.a + 2) * .b), b}'
#+end_src

#+RESULTS:
:results:
{
  "calculated-value": 20,
  "b": 4
}
:end:

** Counting keys or values

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4,
    "c": 3,
    "d": {
        "e": 3
    },
    "f": 6,
    "g": 3
}
EOF
} | jq '[..
        | if type == "object"
          then select(.value)
          else .
          end
        ] | map(select(. == 3)) | length'
#+end_src

#+RESULTS:
:results:
4
:end:

[[https://stackoverflow.com/a/76038755/1829329][StackOverflow]] people say:

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4,
    "c": 3,
    "d": {
        "e": 3
    },
    "f": 6,
    "g": 3,
    "h": 3
}
EOF
} | jq 'def count(s): reduce s as $x (0; .+1); count(tostream | select(length==2 and .[1] == 3))'
#+end_src

#+RESULTS:
:results:
5
:end:

Not sure how it works yet, but we can find out:

#+begin_src sh
{
cat <<EOF
{
    "a": 3,
    "b": 4,
    "c": 3,
    "d": {
        "e": 3
    },
    "f": 6,
    "g": 3,
    "h": 3
}
EOF
} | jq 'tostream'
#+end_src

#+RESULTS:
:results:
[
  [
    "a"
  ],
  3
]
[
  [
    "b"
  ],
  4
]
[
  [
    "c"
  ],
  3
]
[
  [
    "d",
    "e"
  ],
  3
]
[
  [
    "d",
    "e"
  ]
]
[
  [
    "f"
  ],
  6
]
[
  [
    "g"
  ],
  3
]
[
  [
    "h"
  ],
  3
]
[
  [
    "h"
  ]
]
:end:

~tostream~ seems to give multiple lists, which themselves contain a list of keys on the path to the
values. Sort of. For each JSON object it also once gives the list of keys that are the last path of
the object without an associated value. That seems to be its way of expressing, that the object is
finished and the next one will be "streamed".

** Sorting

#+begin_src sh
{
cat <<EOF
[
    [4, 1, 7],
    [8, 5, 2],
    [3, 6, 9]
]
EOF
} | jq 'walk(if type == "array" then sort else . end)'
#+end_src

#+RESULTS:
:results:
[
  [
    1,
    4,
    7
  ],
  [
    2,
    5,
    8
  ],
  [
    3,
    6,
    9
  ]
]
:end:

** WIP Value manipulations

*** Remove all =outputs= from a Juypter notebook

#+begin_src sh
cat my-notebook.ipynb | jq '(..|objects|select(has("outputs"))).outputs = []'
#+end_src

Source: [[https://unix.stackexchange.com/a/476686/129931]]

** Other stuff

*** Quotes

We can also get rid of the double quotes in resulting expressions using the ~-r~ flag, if we do not need them:

#+begin_src sh
docker inspect "${container_name}" | jq -r '.[0] | .Mounts | .[] | {source: .Source , destination: .Destination}'
#+end_src

#+RESULTS:
:results:
{
  "source": "/home/hans/dev/stackfuel/tech-talk/future/jq/foo",
  "destination": "/bar"
}
{
  "source": "/home/hans/dev/stackfuel/tech-talk/future/jq/qux",
  "destination": "/qox"
}
:end:
